// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef CONTENT_SERVICES_AUCTION_WORKLET_TRUSTED_SIGNALS_H_
#define CONTENT_SERVICES_AUCTION_WORKLET_TRUSTED_SIGNALS_H_

#include <map>
#include <memory>
#include <set>
#include <string>
#include <vector>

#include "base/containers/flat_map.h"
#include "base/functional/callback.h"
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_refptr.h"
#include "base/task/sequenced_task_runner.h"
#include "base/time/time.h"
#include "content/common/content_export.h"
#include "content/services/auction_worklet/auction_v8_helper.h"
#include "net/http/http_response_headers.h"
#include "services/network/public/mojom/url_loader_factory.mojom-forward.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "url/gurl.h"
#include "url/origin.h"
#include "v8/include/v8-forward.h"

namespace auction_worklet {

class AuctionDownloader;

// Represents the trusted bidding/scoring signals that are part of the FLEDGE
// bidding system (https://github.com/WICG/turtledove/blob/main/FLEDGE.md).
// Fetches and parses the hosted JSON data files needed by worklets. There are
// separate methods for fetching bidding and scoring signals. A single
// TrustedSignals object can only be used to fetch bidding signals or scoring
// signals, even if a single URL is used for both types of signals.
class CONTENT_EXPORT TrustedSignals {
 public:
  // Contains the values returned by the server.
  //
  // This can be created and destroyed on any thread, but GetSignals() can only
  // be used on the V8 thread.
  class CONTENT_EXPORT Result : public base::RefCountedThreadSafe<Result> {
   public:
    using PriorityVector = base::flat_map<std::string, double>;
    using PriorityVectorMap = std::map<std::string, PriorityVector>;

    // Constructor for bidding signals.
    Result(PriorityVectorMap priority_vectors,
           std::map<std::string, AuctionV8Helper::SerializedValue> bidder_data,
           absl::optional<uint32_t> data_version);

    // Constructor for scoring signals.
    Result(
        std::map<std::string, AuctionV8Helper::SerializedValue> render_url_data,
        std::map<std::string, AuctionV8Helper::SerializedValue>
            ad_component_data,
        absl::optional<uint32_t> data_version);

    explicit Result(const Result&) = delete;
    Result& operator=(const Result&) = delete;

    // Retrieves the `priorityVector` associated with the passed in interest
    // group. `this` must have been generated by fetching bidding signals.
    // Unlike other methods, this does not call into v8, so may be called on any
    // thread. Returns nullptr if there's no matching `priorityVector` in the
    // source dict, and an empty flat_map if the `priorityVector` has no valid
    // entries.
    const TrustedSignals::Result::PriorityVector* GetPriorityVector(
        const std::string& interest_group_name) const;

    // Retrieves the trusted bidding signals associated with the passed in keys,
    // in the format expected by a worklet's generateBid() method. `this` must
    // have been generated by fetching bidding signals. `v8_helper`'s Isolate
    // must be active (in particular, this must be on the v8 thread), and
    // `context` must be the active context. `bidding_signals_keys` must be
    // subsets of the keys provided when creating the TrustedSignals object.
    // Always returns a non-empty value.
    v8::Local<v8::Object> GetBiddingSignals(
        AuctionV8Helper* v8_helper,
        v8::Local<v8::Context> context,
        const std::vector<std::string>& bidding_signals_keys) const;

    // Retrieves the trusted scoring signals associated with the passed in urls,
    // in the format expected by a worklet's scoreAd() method. `this` must have
    // been generated by fetching scoring signals. `v8_helper`'s Isolate must be
    // active (in particular, this must be on the v8 thread), and `context` must
    // be the active context. `render_url` and `ad_component_render_urls` must
    // be subsets of the corresponding sets of GURLs provided when creating the
    // TrustedSignals object. Always returns a non-empty value.
    v8::Local<v8::Object> GetScoringSignals(
        AuctionV8Helper* v8_helper,
        v8::Local<v8::Context> context,
        const GURL& render_url,
        const std::vector<std::string>& ad_component_render_urls) const;

    absl::optional<uint32_t> GetDataVersion() const { return data_version_; }

   private:
    friend class base::RefCountedThreadSafe<Result>;

    ~Result();

    // Per-interest-group priority vectors returned by the trusted bidding
    // server.
    const absl::optional<PriorityVectorMap> priority_vectors_;
    // Map of keys to the associated data for trusted bidding signals.
    const absl::optional<
        std::map<std::string, AuctionV8Helper::SerializedValue>>
        bidder_data_;

    // Map of keys to the associated data for trusted scoring signals.
    const absl::optional<
        std::map<std::string, AuctionV8Helper::SerializedValue>>
        render_url_data_;
    const absl::optional<
        std::map<std::string, AuctionV8Helper::SerializedValue>>
        ad_component_data_;

    // Data version associated with the trusted signals.
    const absl::optional<uint32_t> data_version_;
  };

  using LoadSignalsCallback =
      base::OnceCallback<void(scoped_refptr<Result> result,
                              absl::optional<std::string> error_msg)>;

  explicit TrustedSignals(const TrustedSignals&) = delete;
  TrustedSignals& operator=(const TrustedSignals&) = delete;
  ~TrustedSignals();

  // Constructs a TrustedSignals for fetching bidding signals, and starts the
  // fetch. `trusted_bidding_signals_url` must be the base URL (no query params
  // added).  Callback will be invoked asynchronously once the data has been
  // fetched or an error has occurred. De-duplicates keys when assembling the
  // full URL for the fetch. Fails if the URL already has a query param (or has
  // a location or embedded credentials) or if the response is not JSON. If some
  // or all of the render URLs are missing, still succeeds, and GetSignals()
  // will populate them with nulls.
  //
  // There are no lifetime constraints of `url_loader_factory`.
  static std::unique_ptr<TrustedSignals> LoadBiddingSignals(
      network::mojom::URLLoaderFactory* url_loader_factory,
      std::set<std::string> interest_group_names,
      std::set<std::string> bidding_signals_keys,
      const std::string& hostname,
      const GURL& trusted_bidding_signals_url,
      absl::optional<uint16_t> experiment_group_id,
      scoped_refptr<AuctionV8Helper> v8_helper,
      LoadSignalsCallback load_signals_callback);

  // Just like LoadBiddingSignals() above, but for fetching seller signals.
  static std::unique_ptr<TrustedSignals> LoadScoringSignals(
      network::mojom::URLLoaderFactory* url_loader_factory,
      std::set<std::string> render_urls,
      std::set<std::string> ad_component_render_urls,
      const std::string& hostname,
      const GURL& trusted_scoring_signals_url,
      absl::optional<uint16_t> experiment_group_id,
      scoped_refptr<AuctionV8Helper> v8_helper,
      LoadSignalsCallback load_signals_callback);

 private:
  TrustedSignals(absl::optional<std::set<std::string>> interest_group_names,
                 absl::optional<std::set<std::string>> bidding_signals_keys,
                 absl::optional<std::set<std::string>> render_urls,
                 absl::optional<std::set<std::string>> ad_component_render_urls,
                 const GURL& trusted_signals_url,
                 scoped_refptr<AuctionV8Helper> v8_helper,
                 LoadSignalsCallback load_signals_callback);

  // Starts downloading `url`, which should be the bidding or scoring signals
  // URL with the query parameter correctly set.
  void StartDownload(network::mojom::URLLoaderFactory* url_loader_factory,
                     const GURL& full_signals_url);

  void OnDownloadComplete(std::unique_ptr<std::string> body,
                          scoped_refptr<net::HttpResponseHeaders> headers,
                          absl::optional<std::string> error_msg);

  // Parses the response body on the V8 thread, and extracts values associated
  // with the requested keys.
  static void HandleDownloadResultOnV8Thread(
      scoped_refptr<AuctionV8Helper> v8_helper,
      const GURL& signals_url,
      absl::optional<std::set<std::string>> interest_group_names,
      absl::optional<std::set<std::string>> bidding_signals_keys,
      absl::optional<std::set<std::string>> render_urls,
      absl::optional<std::set<std::string>> ad_component_render_urls,
      std::unique_ptr<std::string> body,
      scoped_refptr<net::HttpResponseHeaders> headers,
      absl::optional<std::string> error_msg,
      scoped_refptr<base::SequencedTaskRunner> user_thread_task_runner,
      base::WeakPtr<TrustedSignals> weak_instance,
      base::TimeDelta download_time);

  // Called from V8 thread.
  static void PostCallbackToUserThread(
      scoped_refptr<base::SequencedTaskRunner> user_thread_task_runner,
      base::WeakPtr<TrustedSignals> weak_instance,
      scoped_refptr<Result> result,
      absl::optional<std::string> error_msg);

  // Called on user thread.
  void DeliverCallbackOnUserThread(scoped_refptr<Result>,
                                   absl::optional<std::string> error_msg);

  // Keys being fetched. For bidding signals, only `bidding_signals_keys_` and
  // `interest_group_names_` are non-null. For scoring signals, only
  // `render_urls_` and `ad_component_render_urls_` are non-null. These are
  // cleared and ownership is passed to the V8 thread once the download
  // completes, as they're no longer on the main thread after that point.
  absl::optional<std::set<std::string>> interest_group_names_;
  absl::optional<std::set<std::string>> bidding_signals_keys_;
  absl::optional<std::set<std::string>> render_urls_;
  absl::optional<std::set<std::string>> ad_component_render_urls_;

  const GURL trusted_signals_url_;  // original, for error messages.
  const scoped_refptr<AuctionV8Helper> v8_helper_;

  LoadSignalsCallback load_signals_callback_;
  std::unique_ptr<AuctionDownloader> auction_downloader_;
  // Used only for metrics; time when download started.
  base::TimeTicks download_start_time_;

  base::WeakPtrFactory<TrustedSignals> weak_ptr_factory{this};
};

}  // namespace auction_worklet

#endif  // CONTENT_SERVICES_AUCTION_WORKLET_TRUSTED_SIGNALS_H_
